﻿//
// Copyright (c) 2011 David Díaz & Fran Pedreira
//
// This file is part of PlanSim.
//
// PlanSim is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PlanSim is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with PlanSim. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using PlanSim.Core;

namespace PlanSim.Interfaz
{
	/// <summary>
	/// Representa gráficamente el cronograma de un conjunto de procesos.
	/// </summary>
    public partial class Cronograma : UserControl
    {
		/// <summary>
		/// Margen que se deja por la parte izquierda e inferior para dibujar los ejes del gráfico.
		/// </summary>
		const int margen = 20;
		const int espaciadoEntreLineas = 40;

		/// <summary>
		/// Indica si estamos arrastrando el ratón (con el botón izquierdo pulsado) sobre el control.
		/// </summary>
		// bool dragging = false;

        /// <summary>
        /// Obtiene o establece el color del fondo.
        /// </summary>
        public Color ColorFondo
        {
            get { return this.BackColor; }
            set { this.BackColor = value; }
        }

        /// <summary>
        /// Obtiene o establece el color del cursor.
        /// </summary>
        public Color ColorCursor
        {
            get { return colorCursor; }
            set { colorCursor = value; }
        }
        Color colorCursor = Color.White;

        /// <summary>
        /// Obtiene o establece el color de los ejes, lineas de separación y valores
        /// </summary>
        public Color ColorMarcas
        {
            get { return colorMarcas; }
            set { colorMarcas = value; }
        }
        Color colorMarcas = Color.LightGray;

        /// <summary>
        /// Obtiene o establece la posición del cursor.
        /// </summary>
        public int PosicionCursor
        {
            get { return posicionCursor; }
			set 
			{
				if (simulacion != null)
				{
					posicionCursor = Math.Min(Math.Max(value, 0), simulacion.MaximoCiclos);
					this.Invalidate();
				}
			}
        }
        int posicionCursor = 0;

        /// <summary>
        /// Establece el origen de los datos.
        /// </summary>
        public Simulacion Simulacion
        {
            set { simulacion = value; Invalidate(); }
        }
		Simulacion simulacion = null;

        /// <summary>
        /// Tamaño de las celdas.
        /// </summary>
		Size dimensionesCelda = new Size(20, 20);

        /// <summary>
		/// Número de barras (ciclos) que se muestran en el cronograma.
        /// </summary>
        int barras = 0;

		/// <summary>
		/// Ciclo que representa la primera barra (por la izquierda) del cronograma.
		/// </summary>
		int barraBase = 0;

        /// <summary>
        /// Número de filas (procesos) que se muestran en el cronograma.
        /// </summary>
        int filas = 0;

		/// <summary>
		/// Proceso que representa la frimera fila (por arriba) del cronograma.
		/// </summary>
		// int filaBase = 0;
		// PENDIENTE: poder hacer scroll vertical

		/// <summary>
		/// Fuente con la que se pintará el texto.
		/// </summary>
		Font font;

        /// <summary>
        /// Constructor.
        /// </summary>
        public Cronograma()
        {
           InitializeComponent();

		   font = new Font("sans serif", 8f);
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
        }

		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);

			// Fuerza el repintado
			this.Invalidate();
		}

        public void Actualizar()
        {
            this.Invalidate();
        }

		protected override void OnPaint(PaintEventArgs e)
		{
			base.OnPaint(e);

			Graphics g = e.Graphics;
            Rectangle bounds = new Rectangle(0, 0, this.Width, this.Height);

            // Valores de los ejes
            barras = (bounds.Width-margen) / dimensionesCelda.Width;
            barraBase = (int)(posicionCursor / barras) * barras;

            filas = ((bounds.Height - margen) / espaciadoEntreLineas);

			g.Clear(this.BackColor);

            if (simulacion != null)
            {
                int y = margen;

				int indiceProceso = 0;
                while (indiceProceso < Math.Min(simulacion.Procesos.Count, filas))
                {
					IProceso proceso = simulacion.Procesos[indiceProceso];

					HatchBrush brushES = new HatchBrush(HatchStyle.BackwardDiagonal, proceso.Color, ColorFondo);
					HatchBrush brushColaES = new HatchBrush(HatchStyle.HorizontalBrick, proceso.Color, ColorFondo);

					for (int ciclo = barraBase; ciclo < (barraBase + barras); ++ciclo)
					{
						int x = ciclo - barraBase;

						EstadoProceso estado = proceso.Historia[ciclo];
						if (estado != null)
						{
							switch (estado.Posicion)
							{
								case EstadoProceso.PosicionProceso.CPU:
									DibujarCeldaCPU(x, y, g, proceso, estado);
									break;

								case EstadoProceso.PosicionProceso.EntradaSalida:
									// Barra de ES
									g.FillRectangle(brushES, new Rectangle(margen + x * dimensionesCelda.Width, y, 1 * dimensionesCelda.Width, dimensionesCelda.Height));
									g.DrawRectangle(new Pen(proceso.Color), new Rectangle(margen + x * dimensionesCelda.Width, y, 1 * dimensionesCelda.Width, dimensionesCelda.Height));
									break;

								case EstadoProceso.PosicionProceso.ColaES:
									// Barra espera de ES
									g.FillRectangle(brushColaES, new Rectangle(margen + x * dimensionesCelda.Width, y, 1 * dimensionesCelda.Width, dimensionesCelda.Height));
									g.DrawRectangle(new Pen(proceso.Color), new Rectangle(margen + x * dimensionesCelda.Width, y, 1 * dimensionesCelda.Width, dimensionesCelda.Height));
									break;

								case EstadoProceso.PosicionProceso.ColaPreparados:
									// Barra espera
									g.FillRectangle(new SolidBrush(proceso.Color), new Rectangle(margen + x * dimensionesCelda.Width, y + 9, 1 * dimensionesCelda.Width, 2));
									break;
							}
						}
					}

                    y += espaciadoEntreLineas;

                    // Pintar siguiente proceso
                    ++indiceProceso;
                }
            }

            DibujarMarcas(g, bounds);
		}

		private void DibujarCeldaCPU(int columna, int fila, Graphics g, IProceso proceso, EstadoProceso estado)
		{
			Brush brushRelleno = new SolidBrush(proceso.Color);
			Pen penBorde = new Pen(proceso.Color);
			Color invertido = Color.FromArgb(255 - proceso.Color.R, 255 - proceso.Color.G, 255 - proceso.Color.B);
			Brush brushTexto = new SolidBrush(invertido);

			SizeF m = g.MeasureString(estado.Pendiente.ToString(), font);

			Rectangle r = new Rectangle(
                columna * dimensionesCelda.Width + margen, 
				fila, 
				dimensionesCelda.Width, dimensionesCelda.Height);

            Point point = new Point(
                (int)(r.X + (dimensionesCelda.Width - m.Width) / 2) + 1,
                (int)(r.Y + (dimensionesCelda.Height - m.Height) / 2) + 1);

			g.FillRectangle(brushRelleno, r);
			g.DrawRectangle(penBorde, r);
			g.DrawString(estado.Pendiente.ToString(), font, brushTexto, point);
		}

        private void DibujarMarcas(Graphics g, Rectangle bounds)
        {
            Pen pen = new Pen(colorMarcas);

            // Dibuja los ejes
            g.DrawLine(pen, new Point(margen, 0), new Point(margen, bounds.Bottom - margen));
            g.DrawLine(pen, new Point(0, bounds.Bottom - margen), new Point(bounds.Right, bounds.Bottom - margen));

            for (int i = 0; i <= barras; ++i)
            {
                SizeF s = g.MeasureString((i + barraBase).ToString(), font);

                g.DrawString(
                    (i + barraBase).ToString(),
                    font,
                    new SolidBrush(colorMarcas),
                    new PointF((i * dimensionesCelda.Width) - s.Width / 2 + 1 + margen, bounds.Bottom - margen + 1));
            }

            // Lineas divisorias
            pen.DashStyle = DashStyle.Dash;
            for (int i = 1; i <= barras; ++i)
                g.DrawLine(pen, new Point(margen + i * dimensionesCelda.Width, 0), new Point(margen + i * dimensionesCelda.Width, bounds.Bottom - margen));

            // Cursor
            g.FillRectangle(new SolidBrush(Color.FromArgb(128, colorCursor)), new Rectangle(margen + (posicionCursor-barraBase) * dimensionesCelda.Width, 0, dimensionesCelda.Width, bounds.Height - margen));
        }

		/*
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

			if (e.Button == MouseButtons.Left)
			{
				dragging = true;
				ActualizarPosicionCursor(e.X, e.Y);
				this.Capture = true;
			}
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
			dragging = false;
            this.Capture = false;
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

			if (dragging)
				ActualizarPosicionCursor(e.X, e.Y);
        }

		void ActualizarPosicionCursor(int x, int y)
		{
			if (x >= 0 && x <= this.Width)
				PosicionCursor = barraBase + (x - margen) / dimensionesCelda.Width;
		}
		*/
    }
}
